﻿using PhoneXamlDirect3DApp1Comp;
using SharpDepend.Classes.ProfileredDatatypes.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using SharpDepend;

namespace SharpDepend.WP8.Renderer
{
    class DirectXRenderer : UserControl, IGraphicsDevice
    {
        private DrawingSurface mDrawingSurface;
        private Direct3DInterop m_d3dInterop = null;
        private PList<SharpDepend.Datatypes.Texture> mTextures;
        private System.Collections.ObjectModel.ReadOnlyCollection<SharpDepend.Datatypes.Texture> mTexturesReadonly;
        private bool mEnable;
        private Dictionary<SharpDepend.Datatypes.Texture, Texture> mNativeTextures = new Dictionary<Datatypes.Texture, Texture>();
        private PList<SharpDepend.Datatypes.Texture> mReadyTextures;
        private float nwidth;
        private float nheight;
        private PList<Texture> mChangedTextures;
        
        private volatile object TextureLocker = new object();
        //private System.Threading.ManualResetEvent mTextureLocker = new System.Threading.ManualResetEvent(true);

        public event Action ResolutionChanged;
        public event Action OnManualRender;

        public DirectXRenderer()
        {
            mChangedTextures = new PList<Texture>("Changed textures", this);

            mReadyTextures = new PList<Datatypes.Texture>("Ready textures", this);

            mDrawingSurface = new DrawingSurface();
            mDrawingSurface.SizeChanged += MDrawingSurface_Loaded;
            Content = mDrawingSurface;

            mTextures = new PList<SharpDepend.Datatypes.Texture>("Textures", this);
            mTexturesReadonly = new System.Collections.ObjectModel.ReadOnlyCollection<SharpDepend.Datatypes.Texture>(mTextures);
        }
        
        private void MDrawingSurface_Loaded(object sender, SizeChangedEventArgs e)
        {
            if (m_d3dInterop == null)
            {
                m_d3dInterop = new Direct3DInterop();

                ReSize((float)mDrawingSurface.ActualWidth, (float)mDrawingSurface.ActualHeight);

                m_d3dInterop.RendererInitialized += M_d3dInterop_RendererInitialized;
                m_d3dInterop.RendererUnitialized += M_d3dInterop_RendererUnitialized;
                m_d3dInterop.OnRender += M_d3dInterop_OnRender;

                // Hook-up native component to DrawingSurface
                mDrawingSurface.SetContentProvider(m_d3dInterop.CreateContentProvider());
            }
        }
        
        private void M_d3dInterop_OnRender()
        {
            var call = OnManualRender;
            if (mEnable && Ready && call != null)
            {
                call();
            }
        }
        
        private void M_d3dInterop_RendererInitialized()
        {
            lock (GraphicsDisplay.Locker)
            {
                // Load early registrated textures.
                for (int i = 0; i < mReadyTextures.Count; i++)
                {
                    var texture = mReadyTextures[i];

                    if (!mNativeTextures.ContainsKey(texture))
                    {
                        RegisterTexture(mReadyTextures[i]);
                    }
                }

                mReadyTextures.Clear();

                Ready = true;
            }
        }

        private void M_d3dInterop_RendererUnitialized()
        {
            lock (GraphicsDisplay.Locker)
            {
                //for (int i = Textures.Count - 1; i >= 0; i--)
                //{
                //    mReadyTextures.Add(Textures[i]);
                //    //UnregisterTexture(Textures[i]);
                //}

                ////Ready = false;
                //mNativeTextures.Clear();
            }
        }

        public void SetViewportSize(double width, double height)
        {
        }

        private void ReSize(float width, float height)
        {
            // Onlt resize if resolution has changed.
            if (nwidth != width || nheight != height)
            {
                while (true)
                {
                    try
                    {
                        var scaleFactor = global::System.Windows.Application.Current.Host.Content.ScaleFactor;

                        // Note: It crash if width and height is 0. InvalidArgumentException.
                        width = (float)Math.Max(8, width);
                        height = (float)Math.Max(8, height);
                        nwidth = (float)Math.Floor(width * Application.Current.Host.Content.ScaleFactor / 100.0f + 0.5f);
                        nheight = (float)Math.Floor(height * Application.Current.Host.Content.ScaleFactor / 100.0f + 0.5f);

                        // Set window bounds in dips
                        m_d3dInterop.WindowBounds = new Windows.Foundation.Size(width, height);

                        // Set native resolution in pixels
                        m_d3dInterop.NativeResolution = new Windows.Foundation.Size(nwidth, nheight);

                        // Set render resolution to the full native resolution
                        m_d3dInterop.RenderResolution = m_d3dInterop.NativeResolution;

                        return;
                    }
                    catch (Exception)
                    {
                    }
                }
            }
        }

        public object Locker
        {
            get { return mDrawingSurface; }
        }

        public bool Enable
        {
            get
            {
                return mEnable;
            }
            set
            {
                mEnable = value;
            }
        }

        public bool RenderTextures { get; set; }

        public int RenderWidth
        {
            get
            {
                return (int)nwidth;
            }
        }

        public int RenderHeight
        {
            get
            {
                return (int)nheight;
            }
        }

        public void RegisterTexture(Datatypes.Texture texture)
        {
            if (mNativeTextures.ContainsKey(texture))
            {
                throw new Exception("Texture already added.");
            }

            if (m_d3dInterop != null && m_d3dInterop.IsRendererInitialized())
            {
                Texture nativeTexture = new Texture();
                nativeTexture.Width = texture.Width;
                nativeTexture.Height = texture.Height;
                nativeTexture.SetData(out texture.Data[0]);
                nativeTexture.ID = texture.ID;

                mNativeTextures.Add(texture, nativeTexture);

                texture.OnBeginUpdate += Texture_OnUpdating;
                texture.OnEndUpdate += Texture_OnUpdated;

                m_d3dInterop.RegisterTexture(nativeTexture);
            }
            else
            {
                mReadyTextures.Add(texture);
            }

            mTextures.Add(texture);
        }
        
        private void Texture_OnUpdating(Datatypes.Texture texture)
        {

        }
        
        private void Texture_OnUpdated(Datatypes.Texture texture)
        {
            lock (TextureLocker)
            {
                if (mNativeTextures.ContainsKey(texture))
                {
                    var nativeTexture = mNativeTextures[texture];
                    
                    // NOTE: Texture is cloning and using the new data array (double buffering)
                    nativeTexture.SetData(out texture.Data[0]);

                    mChangedTextures.Add(nativeTexture);
                }
            }
        }

        public void UnregisterTexture(Datatypes.Texture texture)
        {
            if (mNativeTextures.ContainsKey(texture))
            {
                var nativeTexture = mNativeTextures[texture];

                if (m_d3dInterop == null)
                {
                    mReadyTextures.Remove(texture);
                }
                else
                {
                    m_d3dInterop.UnregisterTexture(nativeTexture);
                }

                texture.OnEndUpdate -= Texture_OnUpdated;

                mNativeTextures.Remove(texture);
            }
            else
            {
                mReadyTextures.Remove(texture);
            }

            mTextures.Remove(texture);
        }

        public void UpdateTexture(Datatypes.Texture texture)
        {

        }

        int test = 0;

        public void BeginRender()
        {
            if (test++ > 0)
            {
                throw new Exception("");
            }

            m_d3dInterop.BeginRender();
        }

        public void Draw(ref Datatypes.Vector4 rectangle, Datatypes.Color color)
        {
            m_d3dInterop.Draw(new Vector4
            {
                X = rectangle.X,
                Y = rectangle.Y,
                Z = rectangle.X2,
                W = rectangle.Y2
            },
            new Color
            {
                R = color.R / 255.0f,
                G = color.G / 255.0f,
                B = color.B / 255.0f,
                A = color.A / 255.0f
            });
        }

        public void Draw(ref Datatypes.Vector4 rectangle, Datatypes.Color color, Datatypes.Texture texture, ref Datatypes.Vector4 destinationTextureUV)
        {
            Texture nativeTexture = null;
            if (texture != null && mNativeTextures.ContainsKey(texture))
            {
                nativeTexture = mNativeTextures[texture];
            }

            m_d3dInterop.Draw(new Vector4
            {
                X = rectangle.X,
                Y = rectangle.Y,
                Z = rectangle.X2,
                W = rectangle.Y2
            },
            new Color
            {
                R = color.R / 255.0f,
                G = color.G / 255.0f,
                B = color.B / 255.0f,
                A = color.A / 255.0f
            },
            nativeTexture,
            new Vector4
            {
                X = destinationTextureUV.X,
                Y = destinationTextureUV.Y,
                Z = destinationTextureUV.X2,
                W = destinationTextureUV.Y2
            }
            );
        }

        public void DrawLine(Datatypes.Vector2 start, Datatypes.Vector2 end, Datatypes.Color color)
        {
            m_d3dInterop.DrawLine(
            new Vector2
            {
                X = start.X,
                Y = start.Y
            },
            new Vector2
            {
                X = end.X,
                Y = end.Y
            },
            new Color
            {
                R = color.R / 255.0f,
                G = color.G / 255.0f,
                B = color.B / 255.0f,
                A = color.A / 255.0f
            });
        }

        public void DrawCircle(Datatypes.Vector2 position, double radius, Datatypes.Color color)
        {
            m_d3dInterop.DrawCircle(
            new Vector2
            {
                X = position.X,
                Y = position.Y
            },
            radius,
            new Color
            {
                R = color.R / 255.0f,
                G = color.G / 255.0f,
                B = color.B / 255.0f,
                A = color.A / 255.0f
            });
        }

        public void DrawTriangles(Datatypes.FastStructList<Datatypes.Vector2> vertices, Datatypes.FastStructList<ushort> indices, Datatypes.FastStructList<Datatypes.Color> colors)
        {
            if (indices.Count > 0)
            {
                IntPtr verticesPtr = System.Runtime.InteropServices.Marshal.UnsafeAddrOfPinnedArrayElement(vertices.ToArray(), 0);
                IntPtr colorsPtr = System.Runtime.InteropServices.Marshal.UnsafeAddrOfPinnedArrayElement(colors.ToArray(), 0);

                long verticesPtr2 = verticesPtr.ToInt64();
                long colorsPtr2 = colorsPtr.ToInt64();

                m_d3dInterop.DrawTriangles(verticesPtr2, vertices.Count, out indices.ToArray()[0], indices.Count, colorsPtr2, colors.Count);
            }
        }
        
        public void EndRender()
        {
            if (--test < 0)
            {
                throw new Exception("");
            }

            m_d3dInterop.EndRender();

            lock (TextureLocker)
            {
                if (mChangedTextures.Count > 0)
                {
                    while (mChangedTextures.Count > 0)
                    {
                        var nativeTexture = mChangedTextures[0];
                        m_d3dInterop.UpdateTexture(nativeTexture);
                        mChangedTextures.RemoveAt(0);
                    }
                }
            }
        }

        public void DisposeDevice()
        {
            lock (GraphicsDisplay.Locker)
            {
                Ready = false;
            }
        }

        public void SetClip(Datatypes.Vector4 plane)
        {
            throw new NotImplementedException();
        }

        public void ResetClip()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Get the graphics display if available.
        /// </summary>
        public IGraphicsDisplay GraphicsDisplay { get; private set; }

        /// <summary>
        /// Calls wqhen this instance has been created.
        /// </summary>
        /// <param name="graphicsDisplay"></param>
        public void Initialize(IGraphicsDisplay graphicsDisplay)
        {
            GraphicsDisplay = graphicsDisplay;
        }

        public System.Collections.ObjectModel.ReadOnlyCollection<Datatypes.Texture> Textures
        {
            get { return mTexturesReadonly; }
        }

        public bool Ready { get; private set; }

        public bool ManualRendering
        {
            get
            {
                return true;
            }
        }
    }
}
